<?PHP
#
#   FILE:  SystemConfiguration.php
#
#   Part of the ScoutLib application support library
#   Copyright 2019-2021 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu
#
# @scout:phpstan

namespace ScoutLib;

use InvalidArgumentException;

/**
 * System configuration field storage manager.
 */
class SystemConfiguration extends Datastore
{
    # ---- PUBLIC INTERFACE --------------------------------------------------

    /**
     * Get universal instance of class.
     * @return self Class instance.
     */
    public static function getInstance()
    {
        if (!isset(static::$Instance)) {
            static::$Instance = new static();
        }
        return static::$Instance;
    }

    /**
     * Get/set information about available system configuration fields.
     * @param array $NewValue System configuration fields list, with field
     *      name for the index, and an associative array with "Type", "Default",
     *      and "Description" entries for each field.  (OPTIONAL)
     * @return array Current system configuration fields list.
     */
    public static function fields(array $NewValue = null): array
    {
        if ($NewValue !== null) {
            static::checkFieldsList($NewValue);
            static::$OurFields = $NewValue;
            if (isset(static::$Instance)) {
                static::$Instance = new static();
            }
        }
        return static::$OurFields;
    }

    /**
     * Get/set name of database table used to store system configuration
     * field values.  (Defaults to "SystemConfiguration".)
     * @param string $NewValue Name of database table in which to save
     *      fields.  (OPTIONAL)
     * @return string Current name of table.
     */
    public static function dbTableName(string $NewValue = null): string
    {
        if ($NewValue !== null) {
            static::$OurDbTableName = $NewValue;
            if (isset(static::$Instance)) {
                static::$Instance = new static();
            }
        }
        return static::$OurDbTableName;
    }

    /**
     * Set override value for array field.  The override value will
     * returned by getArray() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param array $Value New value for field.
     */
    public function overrideArray(string $FieldName, array $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_ARRAY ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Set override value for boolean field.  The override value will
     * returned by getBool() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param bool $Value New value for field.
     */
    public function overrideBool(string $FieldName, bool $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_BOOL ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Set override value for date/time field.  The override value will
     * returned by getDatetime() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param string $Value New value for field.
     */
    public function overrideDatetime(string $FieldName, string $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_DATETIME ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Set override value for float field.  The override value will
     * returned by getFloat() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param float $Value New value for field.
     */
    public function overrideFloat(string $FieldName, float $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_FLOAT ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Set override value for integer field.  The override value will
     * returned by getInt() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param int $Value New value for field.
     */
    public function overrideInt(string $FieldName, int $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_INT ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Set override value for string field.  The override value will
     * returned by getString() in place of any existing value, for the
     * duration of the current page load.
     * @param string $FieldName Name of field.
     * @param string $Value New value for field.
     */
    public function overrideString(string $FieldName, string $Value)
    {
        $this->checkFieldNameAndType($FieldName, [ self::TYPE_STRING ]);
        self::checkValue($this->Fields[$FieldName], $FieldName, $Value);
        $this->OverrideValues[$FieldName] = $Value;
    }

    /**
     * Check whether a field currently has an override value set.
     */
    public function isOverridden(string $FieldName): bool
    {
        if (!isset($this->Fields[$FieldName])) {
            throw new InvalidArgumentException("Invalid field name \""
                    .$FieldName."\".");
        }
        return isset($this->OverrideValues[$FieldName]) ? true : false;
    }

    /**
     * Clear any existing override value for a field.
     * @param string $FieldName Name of field.
     */
    public function clearOverride(string $FieldName)
    {
        if (!isset($this->Fields[$FieldName])) {
            throw new InvalidArgumentException("Invalid field name \""
                    .$FieldName."\".");
        }
        if (isset($this->OverrideValues[$FieldName])) {
            unset($this->OverrideValues[$FieldName]);
        }
    }

    /**
     * Get array field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return array Current value for field, or overrid value if set.
     */
    public function getArray(string $FieldName): array
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, [ self::TYPE_ARRAY ]);
            return $this->OverrideValues[$FieldName];
        }
        return parent::getArray($FieldName);
    }

    /**
     * Get boolean field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return bool Current value for field, or overrid value if set.
     */
    public function getBool(string $FieldName): bool
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, [ self::TYPE_BOOL ]);
            return $this->OverrideValues[$FieldName];
        }
        return parent::getBool($FieldName);
    }

    /**
     * Get date/time field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return int Current value for field, or overrid value if set,
     *      in both cases as a Unix timestamp.
     */
    public function getDatetime(string $FieldName): int
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, [ self::TYPE_DATETIME ]);
            return strtotime($this->OverrideValues[$FieldName]);
        }
        return parent::getDatetime($FieldName);
    }

    /**
     * Get float field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return float Current value for field, or overrid value if set.
     */
    public function getFloat(string $FieldName): float
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, [ self::TYPE_FLOAT ]);
            return $this->OverrideValues[$FieldName];
        }
        return parent::getFloat($FieldName);
    }

    /**
     * Get integer field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return int Current value for field, or overrid value if set.
     */
    public function getInt(string $FieldName): int
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, [ self::TYPE_INT ]);
            return $this->OverrideValues[$FieldName];
        }
        return parent::getInt($FieldName);
    }

    /**
     * Get string field value, with support for overridden values.
     * @param string $FieldName Name of field.
     * @return string Current value for field, or overrid value if set.
     */
    public function getString(string $FieldName): string
    {
        if (isset($this->OverrideValues[$FieldName])) {
            $this->checkFieldNameAndType($FieldName, self::$StringBasedTypes);
            return $this->OverrideValues[$FieldName];
        }
        return parent::getString($FieldName);
    }


    # ---- PRIVATE INTERFACE -------------------------------------------------

    protected static $Instance;
    protected static $OurFields = [];
    protected static $OurDbTableName = "SystemConfiguration";

    protected $OverrideValues = [];

    /**
     * Object constructor.
     */
    protected function __construct()
    {
        parent::__construct(static::$OurFields, static::$OurDbTableName);
    }
}
